package utils;

import java.io.PrintWriter;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.*;

/**
 * 内存心跳 + 数据库超时落地
 * 1. 90 秒无 refresh 即视为离线
 * 2. 每 5 分钟批量扫库（保留原逻辑）
 * 3. 支持注册/刷新/移除
 */
public class TimeoutScheduler {

    /* ---------------- 配置常量 ---------------- */
    private static final long HEARTBEAT_NANOS = TimeUnit.MINUTES.toNanos(5);
    private static final long DB_SCAN_PERIOD    = 5;                            // 库扫描周期（分钟）

    /* ---------------- 线程池 ---------------- */
    private static final ScheduledExecutorService SCHEDULER =
            Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "timeout-scan"));

    /* ---------------- 内存心跳表 ---------------- */
    private static final Map<String, Heartbeat> BEATS = new ConcurrentHashMap<>();

    /* 回调：当检测到超时，通知 Service 做会话清理 */
    public interface TimeoutCallback {
        void onTimeout(String name, String role);
    }
    private static TimeoutCallback callback;

    /* 注册回调（Service 启动时调用一次） */
    public static void setCallback(TimeoutCallback c) { callback = c; }

    /* ---------------- 启动 ---------------- */
    public static void start() {
        // 1. 内存级心跳扫描：每 10 秒一次
        SCHEDULER.scheduleWithFixedDelay(TimeoutScheduler::scanMemory, 10, 10, TimeUnit.SECONDS);

        // 2. 数据库级批量扫描：每 5 分钟一次
        SCHEDULER.scheduleAtFixedRate(TimeoutScheduler::scanDatabase, DB_SCAN_PERIOD, DB_SCAN_PERIOD, TimeUnit.MINUTES);
        System.out.println("[TimeoutScheduler] 启动完成");
    }

    /* ---------------- 内存扫描 ---------------- */
    private static void scanMemory() {
        long now = System.nanoTime();
        BEATS.entrySet().removeIf(e -> {
            Heartbeat hb = e.getValue();
            if (now - hb.lastNanos > HEARTBEAT_NANOS) {
                // 超时
                try {
                    setOfflineInDb(hb.name, hb.role);
                } catch (SQLException ex) {
                    throw new RuntimeException(ex);
                }
                if (callback != null) callback.onTimeout(hb.name, hb.role);
                System.out.println("[Timeout] " + hb.role + " " + hb.name + " 超时离线"+ java.time.LocalDateTime.now());
                return true; // 从内存表删除
            }
            return false;
        });
    }

    /* ---------------- 数据库扫描（保留原逻辑）---------------- */
    private static void scanDatabase() {
        try {
            int u = markTimeoutUsers( 5);// 5 分钟未动
            int a = markTimeoutAdmins(5); // 5 分钟未动
            if (u + a > 0)
                System.out.println("[DB-timeout] 离线用户：" + u + " 人，离线管理员：" + a + " 人");
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /* ---------------- 对外 API ---------------- */
    public static void register(String name, PrintWriter pw, String role) {
        BEATS.put(name, new Heartbeat(name, role, pw));
        refresh(name); // 立即刷新一次
    }

    public static void refresh(String name) {
        Heartbeat hb = BEATS.get(name);
        if (hb != null) hb.lastNanos = System.nanoTime();
    }

    public static void remove(String name) {
        BEATS.remove(name);
    }

    private static void setOfflineInDb(String name, String role) throws SQLException {
        String table;
        if ("USER".equalsIgnoreCase(role)) {
            table = "user";
        } else if ("ADMIN".equalsIgnoreCase(role)) {
            table = "admin";
        } else {
            System.err.println("[TimeoutScheduler] 无效角色: " + role);
            return;
        }

        String sql = "UPDATE " + table + " SET online = 0 WHERE username = ?";
        Connection conn = DbUtil.getConnection();
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1, name);
        ps.execute();
    }

    private static int markTimeoutUsers( int minutes) throws SQLException {
        String sql = "UPDATE user SET online=0 WHERE online=1 AND last_action_time < ?";
        try (Connection conn = DbUtil.getConnection();           // ★ 拿新连接
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setTimestamp(1, Timestamp.valueOf(LocalDateTime.now().minusMinutes(minutes)));
            return ps.executeUpdate();
        }
    }

    private static int markTimeoutAdmins(int minutes) throws SQLException {
        String sql = "UPDATE admin SET online=0 WHERE online=1 AND last_action_time < ?";
        try (Connection conn = DbUtil.getConnection();           // ★ 拿新连接
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setTimestamp(1, Timestamp.valueOf(LocalDateTime.now().minusMinutes(minutes)));
            return ps.executeUpdate();
        }
    }

    public static void stop() { SCHEDULER.shutdown(); }

    /* ---------------- 心跳对象 ---------------- */
    private static class Heartbeat {
        final String name;
        final String role;
        final PrintWriter pw;
        volatile long lastNanos;

        Heartbeat(String name, String role, PrintWriter pw) {
            this.name = name;
            this.role = role.toUpperCase();
            this.pw = pw;
            this.lastNanos = System.nanoTime();
        }
    }
}